package com.core.gwt.ui.client;

import java.util.ArrayList;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.Hyperlink;
import com.google.gwt.user.client.ui.InlineHyperlink;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Widget;

/**
 * A label with inline anchors. The HyperLabel text can contain indexed
 * place-holders in the "{n}" format which will get replaced with anchor
 * elements based on a list of {@link HyperString} arguments
 * 
 * <pre>
 * HyperLabel hyperLabel = new HyperLabel();
 * hyperLabel.setText(&quot;You added {0} as a new client. {1}&quot;, new HyperString(
 * 		clientName, clientToken), new HyperString(&quot;Undo&quot;, undoCommand));
 * </pre>
 * 

 * @see {@link #setText(String, HyperString...)
 */
public class HyperLabel extends Composite implements HasText {
	private static class AnchorClickHandler implements ClickHandler {
		private final Command command;

		public AnchorClickHandler(final Command command) {
			this.command = command;
		}

		@Override
		public void onClick(final ClickEvent event) {
			if (command != null) {
				command.execute();
			}
		}
	}

	private final FlowPanel panel;
	private String text;
	private final InlineLabel first;
	private final ArrayList<Widget> rest;

	/**
	 * Creates a new HyperLabel.
	 */
	public HyperLabel() {
		this(null);
	}

	/**
	 * Creates a new HyperLabel with the specified text.
	 */
	public HyperLabel(final String text) {
		this(null, (HyperString[]) null);
	}

	/**
	 * Creates a new HyperLabel with the specified text and arguments.
	 */
	public HyperLabel(final String text, final HyperString... arguments) {
		panel = new FlowPanel();
		initWidget(panel);
		first = new InlineLabel();
		panel.add(first);
		rest = new ArrayList<Widget>();
		setText(text, arguments);
	}

	/**
	 * Returns the HyperLabel's text.
	 */
	@Override
	public String getText() {
		return text;
	}

	/**
	 * Sets the HyperLabel's text.
	 */
	@Override
	public void setText(final String text) {
		setText(text, (HyperString[]) null);
	}

	/**
	 * Sets the HyperLabel's text. The text may contain place-holders in the
	 * "{n}" format where n is a non-negative integer number. The HyperLabel
	 * uses the specified argument list to substitute anchor elements for the
	 * place holders.<br>
	 * <br>
	 * If the {@link HyperString} argument at the index specified in the
	 * place-holder has a non-null target history token, the HyperLabel
	 * substitutes a {@link Hyperlink} for the place-holder.<br>
	 * <br>
	 * If the {@link HyperString} argument at the index specified in the
	 * place-holder has a null target history token and a non-null command, the
	 * HyperLabel substitues an {@link Anchor} for the place-holder. The
	 * Anchor's href is set to javascript:;. A {@link ClickHandler} which will
	 * execute the command is attached to the Anchor.<br>
	 * <br>
	 * If the {@link HyperString} argument at the index specified in the
	 * place-holder has a null target history token and a null command, the
	 * HyperLabel substitutes a Label for the place-holder. <br>
	 * <br>
	 * If an argument at an index specified by a place-holder does not exist, no
	 * substitution is done.
	 * 
	 * @param text
	 *            the text
	 */
	public void setText(String text, final HyperString... arguments) {
		this.text = text;

		// Clear
		first.setText(null);
		for (final Widget widget : rest) {
			panel.remove(widget);
		}
		rest.clear();

		// Rebuild
		if (text != null) {
			if (arguments == null) {
				first.setText(text);
			} else {
				boolean first = true;
				boolean done = false;
				while (!done) {
					// Split at the next "{n}" place-holder
					final String[] parts = text.split("\\{\\d+\\}", 2);
					assert (parts.length > 0);
					final String plainText = parts[0];
					String placeholderText = null;
					if (parts.length > 1) {
						// Extract the place-holder text
						final int placeholderStart = parts[0].length();
						final int placeholderLength = text.length()
								- (parts[0].length() + parts[1].length());
						final int placeholderEnd = placeholderStart
								+ placeholderLength;
						placeholderText = text.substring(placeholderStart,
								placeholderEnd);
						// Remaining text to process
						text = parts[1];
					} else {
						// No more place-holders; no more text to process
						text = "";
					}
					// Append the plain text portion
					if (first) {
						this.first.setText(plainText);
						first = false;
					} else if (plainText != null && plainText.length() > 0) {
						final InlineLabel label = new InlineLabel(plainText);
						panel.add(label);
						rest.add(label);
					}
					// Substitute a widget for the place holder using the
					// arguments
					if (placeholderText != null) {
						// Get the place-holder index
						final int placeholderIndex = Integer
								.valueOf(placeholderText.substring(1,
										placeholderText.length() - 1));
						if (arguments != null
								&& placeholderIndex < arguments.length) {
							// There is a matching argument
							final HyperString argument = arguments[placeholderIndex];
							final String argText = argument.getText();
							if (argText != null && argText.length() > 0) {
								// Only append if the argument text is not blank
								final String argToken = argument
										.getTargetHistoryToken();
								final Command argCommand = argument
										.getCommand();
								if (argToken != null) {
									// A target history token is specified: use
									// a
									// Hyperlink
									final InlineHyperlink hyperlink = new InlineHyperlink(
											argText, argToken);
									panel.add(hyperlink);
									rest.add(hyperlink);
								} else if (argCommand != null) {
									// No target history token: use an Anchor
									final Anchor anchor = new Anchor(argText);
									// Execute the command upon click
									anchor.addClickHandler(new AnchorClickHandler(
											argCommand));
									panel.add(anchor);
									rest.add(anchor);
								} else {
									final InlineLabel label = new InlineLabel(
											argText);
									panel.add(label);
									rest.add(label);
								}
							}
						} else {
							// No matching argument; no substitution possible;
							// append the place-holder text itself
							final InlineLabel label = new InlineLabel(
									placeholderText);
							panel.add(label);
						}
					}
					// Done if no more text left to process
					done = (text.length() == 0);
				}
			}
		}
	}
}
